
package com.wang.tool.serialize;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
import com.fasterxml.jackson.databind.type.TypeFactory;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Generic Jackson 2-based  that maps {@link Object objects} to JSON using dynamic typing.
 */
public class GenericJacksonUtil {

    private static final ObjectMapper mapperWithTyping;
    private static final ObjectMapper mapperWithoutTyping;

    static {
        mapperWithTyping = new ObjectMapper();
        mapperWithoutTyping = new ObjectMapper();

        // 配置序列化时启用类型信息
        PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
                .allowIfBaseType(Object.class)
                .build();
        mapperWithTyping.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);

        // 配置反序列化时忽略类型信息，关闭默认的多态类型处理
        mapperWithoutTyping.deactivateDefaultTyping();


        mapperWithTyping.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapperWithoutTyping.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        mapperWithTyping.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        mapperWithoutTyping.setSerializationInclusion(JsonInclude.Include.NON_NULL);

        mapperWithTyping.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        mapperWithoutTyping.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);

        mapperWithTyping.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        mapperWithTyping.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);

        mapperWithoutTyping.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
        mapperWithoutTyping.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
    }


    /**
     * @param source
     * @return
     * @throws SerializationException
     * @see GenericJacksonUtil#deserialize(byte[])
     * @see GenericJacksonUtil#deserialize(byte[], Type)
     */
    public static byte[] serialize(Object source) throws SerializationException {
        if (source == null) {
            return new byte[]{};
        }

        try {
            return mapperWithTyping.writeValueAsBytes(source);
        } catch (Throwable e) {
            throw new SerializationException(e, SerializationException.SERIALIZE_ERROR, "Could not write JSON: " + e.getMessage());
        }
    }


    /**
     * @param source
     * @param <T>
     * @return
     * @throws SerializationException
     * @see GenericJacksonUtil#serialize(Object)
     */
    public static <T> T deserialize(byte[] source) throws SerializationException {
        if (source == null || source.length == 0) {
            return null;
        }
        try {
            return (T) mapperWithTyping.readValue(source, Object.class);
        } catch (Throwable e) {
            throw new SerializationException(e, SerializationException.DESERIALIZE_ERROR, "Could not read JSON:  " + e.getMessage());
        }
    }

    /**
     * process
     * bytes -> oriObj -> without_type bytes -> point_type_obj
     *
     * @param source
     * @param type
     * @param <T>
     * @return
     * @throws SerializationException
     * @see GenericJacksonUtil#serialize(Object)
     */
    public static <T> T deserialize(byte[] source, Type type) throws SerializationException {
        if (source == null || source.length == 0) {
            return null;
        }
        T oriObj = deserialize(source);
        if (oriObj == null) {
            return null;
        }
        if (type == null || type == Object.class) {
            return oriObj;
        }
        //        无泛型且继承关系
        if (!(type instanceof ParameterizedType) && type instanceof Class<?> && ((Class<?>) type).isInstance(oriObj)) {
            return oriObj;
        }
        try {
            byte[] bytes = mapperWithoutTyping.writeValueAsBytes(oriObj);
            return (T) mapperWithoutTyping.readValue(bytes, TypeFactory.defaultInstance().constructType(type));
        } catch (Throwable e) {
            throw new SerializationException(e, SerializationException.DESERIALIZE_ERROR, "Could not read JSON: " + e.getMessage());
        }
    }


    /**
     * @param source
     * @return
     * @throws SerializationException
     * @see GenericJacksonUtil#deserializeFromStr(String)
     * @see GenericJacksonUtil#deserializeFromStr(String, Type)
     */
    public static String serializeToStr(Object source) throws SerializationException {
        if (source == null) {
            return null;
        }

        try {
            return mapperWithTyping.writeValueAsString(source);
        } catch (Throwable e) {
            throw new SerializationException(e, SerializationException.SERIALIZE_ERROR, "Could not write JSON: " + e.getMessage());
        }
    }


    /**
     * @param source
     * @param <T>
     * @return
     * @throws SerializationException
     * @see GenericJacksonUtil#serializeToStr(Object)
     */
    public static <T> T deserializeFromStr(String source) throws SerializationException {
        if (source == null || source.isEmpty()) {
            return null;
        }
        try {
            return (T) mapperWithTyping.readValue(source, Object.class);
        } catch (Throwable e) {
            throw new SerializationException(e, SerializationException.DESERIALIZE_ERROR, "Could not read JSON:  " + e.getMessage());
        }
    }

    /**
     * process
     * str -> oriObj -> without_type str -> point_type_obj
     *
     * @param source
     * @param type
     * @param <T>
     * @return
     * @throws SerializationException
     * @see GenericJacksonUtil#serializeToStr(Object)
     */
    public static <T> T deserializeFromStr(String source, Type type) throws SerializationException {
        if (source == null || source.isEmpty()) {
            return null;
        }
        T oriObj = deserializeFromStr(source);
        if (oriObj == null) {
            return null;
        }
        if (type == null || type == Object.class) {
            return oriObj;
        }
//        无泛型且继承关系
        if (!(type instanceof ParameterizedType) && type instanceof Class<?> && ((Class<?>) type).isInstance(oriObj)) {
            return oriObj;
        }

        try {
//            fast then writeValueAsString
            byte[] bytes = mapperWithoutTyping.writeValueAsBytes(oriObj);
            return (T) mapperWithoutTyping.readValue(bytes, TypeFactory.defaultInstance().constructType(type));
        } catch (Throwable e) {
            throw new SerializationException(e, SerializationException.DESERIALIZE_ERROR, "Could not read JSON: " + e.getMessage());
        }
    }



}
